package org.moonlight.jvm.classfile.attribute;

import lombok.Getter;
import org.moonlight.jvm.classfile.ClassReader;
import org.moonlight.jvm.classfile.attribute.debuginfo.LineNumberTableAttribute;
import org.moonlight.jvm.classfile.constant.StaticConstantPool;

/**
 * Code是变长属性，只存在于method_info结构中。Code属性中存放字节码等方法相关信息。其结构定义如下：
 *   Code_attribute {
 *       u2 attribute_name_index
 *       u4 attribute_length
 *       u2 max_stack        // 操作数栈的最大深度；
 *       u2 max_locals       // 局部变量表大小；
 *       u4 code_length
 *       u1 code[code_length]
 *       u2 exception_table_length
 *       {  u2 start_pc
 *          u2 end_pc
 *          u2 handler_pc
 *          u2 catch_type
 *       } exception_table[exception_table_length]   // 异常处理表；
 *       u2 attributes_count
 *       attribute_info attributes[attributes_count] // 属性表；
 *   }
 *
 * @author Moonlight
 * @createTime 2023/8/15 16:44
 **/
@Getter
public class CodeAttribute implements AttributeInfo {

    private final StaticConstantPool scp;
    private int maxStack;
    private int maxLocals;
    private byte[] code;
    private ExceptionTableEntry[] exceptionTable;
    private AttributeInfo[] attributes;

    public CodeAttribute(StaticConstantPool scp) {
        this.scp = scp;
    }

    @Override
    public void readInfo(ClassReader reader) {
        this.maxStack = reader.readUint16();
        this.maxLocals = reader.readUint16();
        int codeLen = (int)reader.readUint32();
        this.code = reader.readBytes(codeLen);
        this.exceptionTable = ExceptionTableEntry.readExceptionTable(reader);
        this.attributes = AttributeInfo.readAttributes(reader, this.scp);
    }

    public LineNumberTableAttribute lineNumberTableAttribute() {
        for (AttributeInfo attr : attributes) {
            if (attr instanceof LineNumberTableAttribute) {
                return (LineNumberTableAttribute) attr;
            }
        }
        return null;
    }

    @Getter
    public static class ExceptionTableEntry {
        private final int startPc;
        private final int endPc;
        private final int handlerPc;
        private final int catchType;

        ExceptionTableEntry(int startPc, int endPc, int handlerPc, int catchType) {
            this.startPc = startPc;
            this.endPc = endPc;
            this.handlerPc = handlerPc;
            this.catchType = catchType;
        }

        static ExceptionTableEntry[] readExceptionTable(ClassReader reader) {
            int exceptionTableLength = reader.readUint16();
            ExceptionTableEntry[] exceptionTable = new ExceptionTableEntry[exceptionTableLength];
            for (int i = 0; i < exceptionTableLength; i++) {
                exceptionTable[i] = new ExceptionTableEntry(reader.readUint16(), reader.readUint16(), reader.readUint16(), reader.readUint16());
            }
            return exceptionTable;
        }
    }

}
